import { v4 as uuidv4 } from 'uuid';

import { type ItemConfig } from '../../data/item';
import { BaseModel } from '../base';
import type { BaseSnapshot } from '../../systems/save';
import { MODEL_NAME } from '../../constants/model-names';
import { EVENTS, type IEventBus } from '../../events';
import { ItemReader } from '../../systems/item/reader';
import type {
  InventorySlotItem,
  InventorySlotItemPayload,
  TempSlot,
} from './type';
import { InventoryFactory } from '../../systems/inventory/factory';
import { cloneDeep } from 'lodash-es';

export interface InventoryV1 extends BaseSnapshot {
  version: 1;
  slots: (InventorySlotItem | null)[];
  capacity: number;
  tempSlots: TempSlot[];
}

export type InventorySnapshot = InventoryV1;

export type InventorySnapshotOld = InventoryV1;

export class InventoryModel extends BaseModel {
  public name = MODEL_NAME.INVENTORY;
  private bus: IEventBus;
  private slots: (InventorySlotItem | null)[] = [];
  private capacity: number = 50;
  /** 临时仓库 */
  private tempSlots: TempSlot[] = [];
  /** 临时仓库大小 */
  private tempCapacity: number = 50;
  /** 临时物品过期时间 */
  private tempDuration: number = 1000 * 60 * 30;

  constructor(bus: IEventBus) {
    super();
    this.bus = bus;
  }

  /** 添加物品 */
  public addItem(itemConfig: ItemConfig, quantity: number) {
    InventoryFactory.createItem(itemConfig, quantity).forEach((newIns) => {
      let addQuantity = quantity;
      if (this.canStack(itemConfig)) {
        for (let i = 0; i < this.capacity && addQuantity > 0; i++) {
          const ins = this.slots[i];
          if (
            ins &&
            ins.itemId === newIns.itemId &&
            this.payloadEqual(ins.payload, newIns.payload) &&
            ins.quantity < itemConfig.maxStack
          ) {
            const canAdd = Math.min(
              addQuantity,
              itemConfig.maxStack - ins.quantity
            );
            ins.quantity += canAdd;
            addQuantity -= canAdd;
          }
        }
      }

      const isFull = this.checkFullAndAddTempItem(newIns);

      if (isFull === false) {
        for (let i = 0; i < this.capacity && addQuantity > 0; i++) {
          if (!this.slots[i]) {
            const put = Math.min(addQuantity, itemConfig.maxStack);
            this.slots[i] = {
              id: uuidv4(),
              itemId: newIns.itemId,
              quantity: put,
              payload: newIns.payload,
            };
            addQuantity -= put;
          }
        }
      }
    });

    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 减少某个格子的物品 */
  public removeItem(slotId: string, amount: number) {
    const ins = this.slots.find((v) => v?.id === slotId);
    if (!ins) return;
    const d = Math.min(amount, ins.quantity);
    ins.quantity -= d;
    if (ins.quantity <= 0) {
      const idx = this.slots.indexOf(ins);
      this.slots[idx] = null;
      this.compact();
    }
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 添加装备物品 */
  public addEquip(inventorySlotItem: InventorySlotItem) {
    const isFull = this.checkFullAndAddTempItem(inventorySlotItem);
    if (isFull === false) {
      for (let i = 0; i < this.capacity; i++) {
        if (!this.slots[i]) {
          this.slots[i] = inventorySlotItem;
          break;
        }
      }
    }
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 移除装备物品 */
  public removeEquip(inventorySlotItem: InventorySlotItem) {
    const id = inventorySlotItem.id;
    for (let i = 0; i < this.capacity; i++) {
      if (this.slots[i]?.id === id) {
        this.slots[i] = null;
        break;
      }
    }
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 按顺序移除物品(用于升级消耗背包物品等功能) */
  public removeItemByItemId(itemId: string, amount: number) {
    let left = amount;

    for (let i = 0; i < this.capacity && left > 0; i++) {
      const ins = this.slots[i];
      if (!ins || ins.itemId !== itemId) continue;

      const deduct = Math.min(left, ins.quantity);
      ins.quantity -= deduct;
      left -= deduct;

      if (ins.quantity <= 0) {
        this.slots[i] = null;
      }
    }

    this.compact();
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  public getItem(slotId: string): InventorySlotItem | null {
    return this.slots.find((v) => v?.id === slotId) ?? null;
  }

  public getItems(): { ins: InventorySlotItem; itemConfig: ItemConfig }[] {
    const items = this.slots
      .filter((v) => v !== null)
      .map((ins) => ({
        ins: ins,
        itemConfig: ItemReader.getConfig(ins.itemId)!,
      }));
    return items;
  }

  /** 压缩背包，将物品向前移动填充空位 */
  public compact(): void {
    const newSlots: (InventorySlotItem | null)[] = new Array(
      this.capacity
    ).fill(null);
    let writeIndex = 0;

    for (let i = 0; i < this.capacity; i++) {
      const item = this.slots[i];
      if (this.isValidInventoryItem(item)) {
        newSlots[writeIndex++] = item;
      }
    }

    this.slots = newSlots;
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  public checkFull(): boolean {
    return this.getUseCapacity() >= this.capacity;
  }

  /** 判断背包是否已满，已满添加临时物品 */
  private checkFullAndAddTempItem(item: InventorySlotItem) {
    if (this.checkFull()) {
      this.bus.emit(
        EVENTS.GAME_MESSAGE,
        '背包已满，物品被放入临时仓库，请及时处理，超时将被丢弃'
      );
      this.addTempItem(item);
      return true;
    }
    return false;
  }

  /** 类型守卫，检查是否为有效的背包物品 */
  private isValidInventoryItem(
    item?: InventorySlotItem | null
  ): item is InventorySlotItem {
    return (
      item !== null &&
      item !== undefined &&
      typeof item.id === 'string' &&
      typeof item.itemId === 'string' &&
      typeof item.quantity === 'number'
    );
  }

  /** 添加临时物品 */
  public addTempItem(item: InventorySlotItem) {
    if (this.tempSlots.length >= this.tempCapacity) {
      this.bus.emit(
        EVENTS.GAME_MESSAGE,
        '临时背包已满，请及时处理，物品将被丢弃'
      );
      return;
    }
    this.tempSlots.push({
      id: uuidv4(),
      createdAt: Date.now(),
      ins: item,
    });
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 临时物品提取到背包 */
  public extractTempItem(tempSlot: TempSlot) {
    const ins = tempSlot.ins;
    const isFull = this.checkFullAndAddTempItem(ins);
    if (isFull === false) {
      for (let i = 0; i < this.capacity; i++) {
        if (!this.slots[i]) {
          this.slots[i] = ins;
          break;
        }
      }
    }
    this.tempSlots = this.tempSlots.filter((v) => v.id !== tempSlot.id);
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 检查临时背包物品是否过期，过期移除 */
  public checkTempItemExpired() {
    this.tempSlots = this.tempSlots.filter((v) => {
      return Date.now() - v.createdAt < this.getTempDuration();
    });
    this.bus.emit(EVENTS.INVENTORY_STATE_UPDATE);
  }

  /** 获取临时物品过期时间 */
  public getTempDuration() {
    return this.tempDuration;
  }

  /** 获取临时背包列表 */
  public getTempSlots() {
    return cloneDeep(this.tempSlots);
  }

  /** 获取背包最大容量 */
  public getCapacity(): number {
    return this.capacity;
  }

  /** 获取背包已用容量 */
  public getUseCapacity(): number {
    return this.slots.filter((v) => v !== null).length;
  }

  /** 获取背包某个物品的总数 */
  public getQuantity(itemId: string): number {
    return this.slots.reduce((acc, v) => {
      if (v !== null && v.itemId === itemId) {
        acc += v.quantity;
      }
      return acc;
    }, 0);
  }

  public override save(): InventorySnapshot {
    return {
      version: 1,
      slots: this.slots,
      capacity: this.capacity,
      tempSlots: this.tempSlots,
    };
  }

  public override load(snapshot: InventorySnapshot) {
    this.capacity = snapshot.capacity;
    this.slots = snapshot.slots || [];
    this.tempSlots = snapshot.tempSlots || [];
  }

  private canStack(cfg: ItemConfig): boolean {
    return cfg.maxStack > 1;
  }

  private payloadEqual(
    a?: InventorySlotItemPayload,
    b?: InventorySlotItemPayload
  ): boolean {
    return JSON.stringify(a) === JSON.stringify(b);
  }
}
